
/**
 * @file	logout.c
 * @brief	日志文件函数.
 * @author	Alan
 * @date	2008-8-21 13:13:17
 * @note	可以写入文件,也可以到控制台. 文件达到上限时,会生成新的日志文件.
 * @see		logout.h
 * @version  $Id$
**/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>

#include "logout.h"

static FILE *g_fp = NULL;       /// 全局日志文件句柄.
static int g_flag = 0;          /// 日志输出标记.
static char g_log_buf[LOG_BUF_CNT][LOG_BUF_SIZE];   /// 日志缓存.
static int g_buf_idx = 0;       /// 当前日志数量.
static int g_log_level = 0;     /// 日志级别 0~9. 默认为0.
static int g_file_max = 0;      /// 一个日志文件最大行数.
static char g_label[128];       /// 日志文件名前的标记.


/**
 * @brief  打开日志功能.
 * @param  label 构成日志文件的串.
 * @param  level 日志级别,0~9,数字越小,级别越高.
 * @param  flag  日志文件输出位置标记. 参见头文件宏定义.
 * @return 返回 0.
 * @see	   flag 的值参见头文件宏定义.
**/
int logopen(const char *label, int level, int flag)
{
    char filename[256];         // 文件名.
    struct tm *timeval;         // 格式化时间,构成文件名.
    time_t timet;               // 格式化时间,构成文件名.
    size_t len;                 // 格式化后的时间串长度.
    char strtime[32];           // 格式化后的时间串.

    /// 如果是再次打开,就不用再初始化这些内容了.
    if (g_log_level != level && g_flag != flag) {
        /// 设置日志级别.
        g_log_level = level;

        /// 设置标识.
        bzero(g_label, sizeof(g_label));
        strncpy(g_label, label, sizeof(g_label) - 1);
    } else if (g_fp != NULL) {
        return 0;
    }

    /// 打开日志文件.
    g_flag = flag;
    switch (g_flag) {
    case LOGFILE:
        /// 构造文件名: label_日期_时间.log
        time(&timet);
        timeval = localtime(&timet);
        bzero(strtime, sizeof(strtime));
        len = strftime(strtime, sizeof(strtime) - 1, "%F_%T", timeval);
        bzero(filename, sizeof(filename));
        snprintf(filename, sizeof(filename) - 1, "%s_%s.log", label, strtime);

        if (g_fp != NULL) {
            fclose(g_fp);
        }
        g_fp = fopen(filename, "wp");
        if (g_fp == NULL) {
            g_flag = NOLOG;
        } else {
            g_buf_idx = 0;
            g_file_max = 0;
            fprintf(stderr, "Create new log file.\n");
        }
        break;
    case LOGSTDOUT:
        g_fp = stderr;
        break;
    case NOLOG:
    default:
        g_flag = NOLOG;
        break;
    }

    return 0;
}

/**
 * @brief  输出并清除日志缓存.
 * @return 无
**/
static void log_flush(void)
{
    int i;                      // 用于for循环的临时变量.

    if (g_flag == NOLOG) {
        return;
    }
    /// 输出日志.重置日志数量.
    for (i = 0; i < g_buf_idx; i++) {
        fprintf(g_fp, g_log_buf[i]);
    }
    g_buf_idx = 0;

    /// 清理缓存.
    fflush(g_fp);

    return;
}

/**
 * @brief  关闭日志文件.
 * @return 返回 0.
**/
int logclose(void)
{
    /// 刷新日志缓存.
    log_flush();

    /// 关闭文件.
    if (g_flag == LOGFILE && g_fp != NULL) {
        fflush(g_fp);
        fclose(g_fp);
        g_fp = NULL;
    }

    return 0;
}

/**
 * @brief  日志输出函数.
 * @param  file 	文件名.
 * @param  func		函数名.
 * @param  line		行号.
 * @param  level 	日志级别,0~9,数字越小,级别越高.
 * @param  fmt		格式, 参见 printf().
 * @param  ...		变长参数
 * @return 0:正常返回; -1:级别错误; -2:数据超长.
 * @note   支持变长参数,为了方便,调用LOGOUT宏,代替这个函数.\n
 		   每条记录的最大长度为	LOG_BUF_SIZE-1, 大于这个数字的记录被忽略.
 * @see	   格式参数的详细信息参见 printf().
**/
int logout(const char *file, const char *func, const int line,
           const int level, const char *fmt, ...)
{
    va_list vl;                 // 用于变长参数.
    struct tm *timeval;         // 用来格式化时间.
    time_t timet;               // 用来格式化时间.
    size_t len;                 // 字符串长度.
    char buf[LOG_BUF_SIZE];     // 临时存放字符串.

    if (g_flag == NOLOG || level > g_log_level) {
        return 0;
    }
    /// 判断日志的级别,是否打印.
    if (level < 0 || level > 9) {
        return -1;
    }

    bzero(g_log_buf[g_buf_idx], LOG_BUF_SIZE);

    /// 加入构造的时间串.
    bzero(buf, sizeof(buf));
    time(&timet);
    timeval = localtime(&timet);
    len = strftime(buf, sizeof(buf) - 1, "[%F %T] ", timeval);
    if (len > LOG_BUF_SIZE) {
        return -2;
    }
    strncat(g_log_buf[g_buf_idx], buf, len);

    /// 加入打印日志的位置.
    bzero(buf, sizeof(buf));
    len = snprintf(buf, sizeof(buf) - 1, "%s : %s() : %d :: [%d] ", file, func, line, level);
    if (len > LOG_BUF_SIZE - strlen(g_log_buf[g_buf_idx])) {
        return -2;
    }
    strncat(g_log_buf[g_buf_idx], buf, len);

    /// 加入日志内容.
    bzero(buf, sizeof(buf));
    va_start(vl, fmt);
    len = vsnprintf(buf, sizeof(buf) - 1, fmt, vl);
    va_end(vl);
    if (len > LOG_BUF_SIZE - strlen(g_log_buf[g_buf_idx])) {
        return -2;
    }
    strncat(g_log_buf[g_buf_idx], buf, strlen(buf));

    /// 日志输出,如果是控制台则直接输出,如果是文件则入缓存.
    if (g_flag == LOGSTDOUT && g_fp != NULL) {
        fprintf(g_fp, g_log_buf[g_buf_idx]);
    } else if (g_flag == LOGFILE) {
        g_buf_idx++;

        /// 缓存满则输出缓存内容.
        if (g_buf_idx >= LOG_BUF_CNT) {
            g_file_max += g_buf_idx;

            log_flush();

            /// 打开一个新的日志文件.
            if (g_file_max >= LOG_FILE_SIZE) {
                fclose(g_fp);
                g_fp = NULL;
                logopen(g_label, g_log_level, g_flag);
            }
        }
    }

    return 0;
}

void logset(int signo)
{
    fprintf(stderr, "Receive signol %d.\n", signo);

    if (g_flag == NOLOG) {
        fprintf(stderr, "Open log.\n");
        logopen("hello", 5, LOGFILE);
    } else {
        fprintf(stderr, "Close log.\n");
        logclose();
        g_fp = NULL;
        g_flag = NOLOG;
    }

    return;
}

void logregsig()
{
    signal(SIGTERM, logset);
}

#if 0
/// 用于测试日志函数.
int main(int argc, char *argv[])
{
    int i = 0;

    LOGREGSIG();

    //LOGOPEN(argv[0], 5, NOLOG);
    //LOGOPEN(argv[0], 5, LOGSTDOUT);
    LOGOPEN(argv[0], 5, LOGFILE);

    for (i = 0; i < 1024; i++) {
        usleep(1000 * 20);
        fprintf(stderr, "loop %d\n", i);
        LOGOUT(0, "Are you kidding me? %d\n", i);
        LOGOUT(3, "%s %d\n", "You are a nice friend, you won't even lent me $5!", i);
        LOGOUT(5, "%s %d\n",
               "Did you boys have a nice Christmas?; Did you boys have a nice time at Christmas?",
               i);
    }

    LOGCLOSE();

    return 0;
}
#endif                          // #if 0
